import type { DragElOptions, StartPostion } from "./drag-element";
import { registerDragElement } from "./drag-element";

// tt=top-top tb=top-bottom ...
type LineType = "tt" | "tb" | "bt" | "bb" | "ll" | "lr" | "rl" | "rr";

interface DraglineOptions {
  activeClassName: string;
  threshold: number; // 吸附
  lineTypes: LineType[];
  preventDrag: boolean; // 禁用
  dragLineClassName: string;
  otherAlignedClassName: string;
}

interface ElementPositon {
  l: number;
  r: number;
  t: number;
  b: number;
  // c
  // c: number;
}
type Direction = keyof ElementPositon;

// 其他没有拖动动的元素-拖动的元素
// "tt", "tb", "bt", "bb" 水平方向
// "ll", "lr", "rl", "rr" 垂直方向
const lineTypes: LineType[] = ["tt", "tb", "bt", "bb", "ll", "lr", "rl", "rr"];

export class Dragline {
  dragElements: HTMLElement[] = [];
  activeElement: HTMLElement | null = null;
  elementToPositionMap: Map<HTMLElement, ElementPositon> = new Map(); // 存放拖拽元素时，辅助线的位置信息
  options: DraglineOptions = {
    activeClassName: "active",
    otherAlignedClassName: "other-aligned",
    threshold: 10, // 显示辅助线的时机
    // lineTypes: ["tt", "bb", "ll", "rr"],
    lineTypes,
    preventDrag: false,
    dragLineClassName: "dragline",
  };
  destories: (() => void)[] = [];
  dragContainer: HTMLElement | null = null;
  targetToLineMap: Map<HTMLElement, Map<LineType, HTMLElement>> = new Map(); // 辅助线，防止拖拽时候，重复添加辅助线

  constructor(options: Partial<DraglineOptions> = {}) {
    this.options = {
      ...this.options,
      ...options,
    };
  }

  registerDragElement(options: DragElOptions) {
    const container = options.container;
    this.dragElements.push(container);

    this.setElementPositon(container);

    // 初始化辅助线位置
    this.initElementPosition(container);

    const onDragStart = (ev: MouseEvent, startPos: StartPostion) => {
      this.setActiveElement(container);
      options.onDragStart?.(ev, startPos);
      // 按下时候就计算
      this.caucateLine();
    };

    const onDrag = (ev: MouseEvent, startPos: StartPostion) => {
      // 更新辅助线位置
      this.updateElementPosition(container);

      // 开始计算辅助线位置
      this.caucateLine();

      options.onDrag?.(ev, startPos);
    };

    const onDragEnd = (ev: MouseEvent, startPos: StartPostion) => {
      this.removeActiveElement();
      this.clearAllLine();

      // 更新辅助线位置
      this.updateElementPosition(container);

      options.onDragEnd?.(ev, startPos);
    };

    const newOptions: DragElOptions = {
      ...options,
      onDragStart,
      onDrag,
      onDragEnd,
    };

    const removeEventListener = registerDragElement(
      newOptions,
      this.options.preventDrag
    );

    const destory = () => {
      removeEventListener();
      this.elementToPositionMap.delete(container);
      this.destories = this.destories.filter((item) => item !== destory);
      this.clearTargetLine(container);
    };

    this.destories.push(destory);

    return destory;
  }

  destoryAll() {
    this.destories.forEach((destory) => destory());
    this.elementToPositionMap.clear();
    this.targetToLineMap.clear();
  }

  registerDragContainer(el: HTMLElement) {
    this.dragContainer = el;
  }

  private isHorizantal(lineType: LineType) {
    if (lineType.includes("b") || lineType.includes("t")) {
      // 水平方向
      return true;
    }
    // 垂直方向
    return false;
  }

  private clearAllLine() {
    this.targetToLineMap.forEach((lineTypeToLines) => {
      lineTypeToLines.forEach((line) => {
        line.remove();
      });
    });

    const otherElements = this.getOtherElements();
    otherElements.forEach((el) => {
      el.classList.remove(this.options.otherAlignedClassName);
    });

    this.targetToLineMap.clear();
  }

  private clearTargetLine(target: HTMLElement) {
    const lineTypeToLines = this.targetToLineMap.get(target);
    if (lineTypeToLines) {
      lineTypeToLines.forEach((line) => {
        line.remove();
      });
      this.targetToLineMap.delete(target);
    }
  }

  private setElementPositon(el: HTMLElement) {
    const elPosition: ElementPositon = {
      l: el.offsetLeft,
      r: el.offsetLeft + el.offsetWidth,
      t: el.offsetTop,
      b: el.offsetTop + el.offsetHeight,
    };
    this.elementToPositionMap.set(el, elPosition);
  }

  private setActiveElement(el: HTMLElement) {
    this.activeElement = el;
    this.activeElement.classList.add(this.options.activeClassName);
  }

  private removeActiveElement() {
    if (this.activeElement) {
      this.activeElement.classList.remove(this.options.activeClassName);
    }
  }

  private getOtherElements() {
    // 排除非拖动的元素，拿到其他元素
    return this.dragElements.filter((item) => item !== this.activeElement);
  }

  /**
   * 更新辅助线位置
   *
   * @param el 辅助线
   */
  private updateElementPosition(el: HTMLElement) {
    const position = this.elementToPositionMap.get(el);
    if (position) {
      position.l = el.offsetLeft;
      position.r = el.offsetLeft + el.offsetWidth;
      position.t = el.offsetTop;
      position.b = el.offsetTop + el.offsetHeight;
    }
  }

  private initElementPosition(el: HTMLElement) {
    this.updateElementPosition(el);
  }

  private createDragLineElement() {
    const draglineEl = document.createElement("div");
    draglineEl.style.position = "absolute";
    draglineEl.style.boxSizing = "border-box";
    draglineEl.classList.add(this.options.dragLineClassName);
    return draglineEl;
  }

  private caucateLine() {
    const otherElements = this.getOtherElements();
    const activeElPositon = this.elementToPositionMap.get(this.activeElement!)!;

    this.dragContainer =
      this.dragContainer || (this.activeElement!.offsetParent as HTMLElement);

    otherElements.forEach((target) => {
      const targetElPosition = this.elementToPositionMap.get(target)!;

      this.options.lineTypes.forEach((lineType) => {
        const [activeDirection, targetDirection] = lineType.split("") as [
          Direction,
          Direction
        ];

        // 小于某一个数值，显示辅助线
        const targetPos = targetElPosition[targetDirection];
        const distiance = Math.abs(
          targetPos - activeElPositon[activeDirection]
        );
        if (distiance < this.options.threshold * 2) {
          target.classList.add(this.options.otherAlignedClassName);
          // 显示辅助线
          console.log("显示辅助线");

          if (this.isHorizantal(lineType)) {
            // 是水平方向
            if (distiance < this.options.threshold) {
              // 吸附效果
              this.activeElement!.style.top = `${
                activeDirection === "b"
                  ? targetPos - this.activeElement!.clientHeight
                  : targetPos
              }px`;
            }
          } else {
            // 是垂直方向
            if (distiance < this.options.threshold) {
              // 吸附效果
              this.activeElement!.style.left = `${
                activeDirection === "r"
                  ? targetPos - this.activeElement!.clientWidth
                  : targetPos
              }px`;
            }
          }

          // 创建辅助线
          let draglineEl: HTMLElement;
          if (!this.targetToLineMap.has(target)) {
            const lineTypeToLines = new Map<LineType, HTMLElement>();
            draglineEl = this.createDragLineElement();

            lineTypeToLines.set(lineType, draglineEl);
            this.targetToLineMap.set(target, lineTypeToLines);
          } else {
            const lineTypeToLines = this.targetToLineMap.get(target)!;
            if (lineTypeToLines.has(lineType)) {
              draglineEl = lineTypeToLines.get(lineType)!;
            } else {
              draglineEl = this.createDragLineElement();
              lineTypeToLines.set(lineType, draglineEl);
            }
          }

          if (this.isHorizantal(lineType)) {
            // 是水平方向
            // 辅助线的位置
            const xAxis = [
              activeElPositon.l,
              activeElPositon.r,
              targetElPosition.l,
              targetElPosition.r,
            ];
            const minX = Math.min(...xAxis);
            const maxX = Math.max(...xAxis);
            // 辅助线的宽度
            const draglineElWidth = maxX - minX;
            draglineEl.style.height = "0px";
            draglineEl.style.width = `${draglineElWidth}px`;
            draglineEl.style.left = `${minX}px`;
            draglineEl.style.top = `${targetPos}px`;
            draglineEl.style.borderBottom = "1px dashed blue";
          } else {
            // 是垂直方向
            // 辅助线的位置
            const yAxis = [
              activeElPositon.t,
              activeElPositon.b,
              targetElPosition.t,
              targetElPosition.b,
            ];
            const minY = Math.min(...yAxis);
            const maxY = Math.max(...yAxis);
            // 辅助线的宽度
            const draglineElHeight = maxY - minY;
            draglineEl.style.width = "0px";
            draglineEl.style.height = `${draglineElHeight}px`;
            draglineEl.style.left = `${targetPos}px`;
            draglineEl.style.top = `${minY}px`;
            draglineEl.style.borderLeft = "1px dashed blue";
          }

          if (!draglineEl.parentElement) {
            // 把辅助线添加到容器中
            this.dragContainer!.appendChild(draglineEl);
          }
        } else {
          const lineTypeToLines = this.targetToLineMap.get(target)!;
          if (lineTypeToLines?.has(lineType)) {
            const draglineEl = lineTypeToLines.get(lineType)!;
            draglineEl.remove();
            lineTypeToLines.delete(lineType);

            if (lineTypeToLines.size === 0) {
              target.classList.remove(this.options.otherAlignedClassName);
            }
          }
          // this.clearTargetLine(target);
        }
      });
    });
  }
}
